'use strict';
 /**
  * our own wrapper that store the constructed model by Sequelize
  * also we could provide some additional methods for use
  * basically we are going to store those models in the expressApp.locals

NOTE: this is the complete implementation of our own model container class

KEYS: switch between database drivers based on the config files
NEXT: allow end user to mix and match database using the model.config method

DESIGN: basically its a jquery style chainable object, each call return itself.
and mix with the q for promise system.
  */
var _ = require('lodash');
var path = require('path');
var chalk = require('chalk');
// array of data types
var choices = {
    'RDBMS': [
        {name: 'String'   , value: 'STRING'  , canDef: true , ex: 'STRING(32)' , input: 1},
        {name: 'Text'     , value: 'TEXT'},
        {name: 'Interger' , value: 'INTEGER'},
        {name: 'BIGINT'   , value: 'BIGINT'  , canDef: true , ex: 'BIGINT(11)' , input: 1},
        {name: 'FLOAT'    , value: 'FLOAT'   , canDef: true , ex: 'FLOAT(11) or FLOAT(11,12)' , input: 2},
        {name: 'DOUBLE'   , value: 'DOUBLE'  , canDef: true , ex: 'DOUBLE(11) or DOUBLE(11,12)' , input: 2},
        {name: 'DECIMAL'  , value: 'DECIMAL' , canDef: true , ex: 'DECIMAL(10 , 2)' , input: 1},
        {name: 'Date'     , value: 'DATE'},
        {name: 'Boolean'  , value: 'BOOLEAN'},
        {name: 'ENUM'     , value: 'ENUM' , canDef: true , ex: 'NUM(value 1 , value 2 , value 3)' , input: 'n'},
        {name: 'UUID'     , value: 'UUID' , canDef: true , defaults: ['Sequelize.UUIDV1' , 'Sequelize.UUIDV4']},
        {name: 'BLOB'     , value: 'BLOB' , canDef: true , input: ['tiny' , 'medium' , 'long']}
    ],
    'NOSQL': [
        {name: 'String'   , value: 'String'},
        {name: 'Number'   , value: 'Number'},
        {name: 'Boolean'  , value: 'Boolean'},
        {name: 'Array'    , value: 'Array'},
        {name: 'Buffer'   , value: 'Buffer'},
        {name: 'Date'     , value: 'Date'},
        {name: 'ObjectId' , value: 'ObjectId'},
        {name: 'Mixed'    , value: 'Mixed'}
    ],
    'NEWSQL': []
};
// this are postgresql only options
var PostgresqlOnly = [
        {name: 'ARRAY' , value: 'ARRAY' , defaults: 'Sequelize.TEXT'},
        {name: 'JSON'  , value: 'JSON' },
        {name: 'JSONB' , value: 'JSONB'},
        {name: 'REAL'  , value: 'REAL' , canDef: true , ex: 'REAL(11) or REAL(11,12)' , input: 2}
    ];

// Sequelize = require('sequelize'),
/**
 * constructor
 * we load the config here then determine what type of database we need to use
 */
var Model = function(panes , connection)
{
    var config = panes.get('config');
    connection = connection || 'default';
    // store the config
    this.connection = connection;
    this.databaseType = config.db[connection].type;
    this.databaseDriver = config.db[connection].driver;
    this.panes = panes;
};

/**
 * load the respective
 */
Model.prototype.__getModelCls = function()
{
    var cls;
    switch (this.databaseType) {
        case 'RDBMS':
            cls = require('sequelize');
        break;
        case 'NOSQL':
            cls = require('mongoose');
        break;
        default:
            // throw it
    }
    return cls;
};

/**
 * map the respective datatype to our own internal map
 */
Model.prototype.__generateDataTypes = function()
{
    if (this.databaseDriver==='couchdb' || this.databaseDriver==='redis') {
        return; // there is no data definiton for couchdb
    }
    var _path = 'DataType.' + this.databaseType + '.' + this.databaseDriver;
    this.dt = this.dataType = this.panes.get(_path);
    if (!this.dt) {
        return;
    }
    var cls = this.__getModelCls(),
        _this = this;
    choices[this.databaseType].forEach(function(data)
    {
        _this.dt[data.name] = cls[data.name];
    });
    if (this.databaseDriver==='postgresql') {
        PostgresqlOnly.forEach(function(data)
        {
            _this.dt[data.name] = cls[data.name];
        });
    }
    this.dataType = this.dt;
    this.panes.set(_path , this.dataType);
};

/**
 * currying the original schema methods, but return the same thing.
 * just that got a the dt
 */
Model.prototype.schema = function(name , def)
{
    if (this.databaseType==='NOSQL') {
        var cls = this.__getModelCls();
        var schema = cls.Schema;
        this[name] = new schema(def);
    }
    else {
        this[name] = def;
    }
    return this[name];
};

/**
 * apply methods to the schema, in fact this only apply to mongoose
 * @param (object) the schema object
 * @param (object) hash table of functions to apply
 * @return (object) the transformed schema
 */
Model.prototype.applyMethods = function(schema , methods)
{
    if (this.databaseDriver==='mongodb') {
        var can = ['methods' , 'static'];
        _.each(methods , function(func , c) {
            switch (c) {
                case 'pre':
                    var calls = [];
                    if (_.isObject(func)) {
                        _.forEach(func , function(ff , cc)
                        {
                            calls.push({name: cc , func: ff});
                        });
                    }
                    if (!_.isArray(func)) {
                        console.log(chalk.red('Wrong pre methods formatting!'));
                        return;
                    }
                    else {
                        calls = func;
                    }
                    _.forEach(calls , function(call)
                    {
                        schema.pre(call.name , call.func);
                    });
                break;
                default:
                    if (_.indexOf(can , c)>-1) {
                        _.forEach(func , function(call , name)
                        {
                            schema[c][name] = call;
                        });
                    }
            }
        });
    }
    return schema;
};
/**
 * seperate the create model and the schema
 * @param (string) name of the model (remember single!)
 * @param (object) the schema defintion
 * @param (object) methods to could apply to the schema, based on their respective library implementation
 * @return (object) new model object
 */
Model.prototype.factory = function(name , def , methods)
{
    // generate the database type for this particular model
    // this.__generateDataTypes();
    // generate schema
    var schema = this.schema(name, def);

    if (methods) {
        schema = this.applyMethods(schema , methods);
    }

    var newModel;
    // binding them to their respective objects
    switch (this.databaseType) {
        case 'NOSQL':
            switch (this.databaseDriver) {
                case 'mongodb':
                    newModel = this.panes.get('connection.' + this.connection).model(name , schema);
                break;
                case 'couchdb':
                    newModel = this.panes.get('connection.' + this.connection).database(name);
                    newModel.exists(function (err, exists) {
                        if (err) {
                            console.log('error', err);
                        } else if (exists) {
                            console.log(name + ' already exists');
                        } else {
                            console.log('creating new database: ' + name);
                            newModel.create(); /* populate design documents */
                        }
                    });
                break;
                case 'redis': // redis doesn't have model so we just grab the connection back
                    newModel = this.panes.get('connection.' + this.connection);
                break;
                default:
                    // nothing to do here
            }
        break;
        case 'RMDBS':
            newModel = this.panes.get('connection.' + this.connection).define(schema);
        break;
    }
    // storing it back to our own storage
    this.panes.set('models.' + name , newModel);
    // return this model
    return newModel;
};

/**
 * exporting it back
 */
module.exports = Model;
// -- EOF --
